package gateway

import (
	"fmt"
	"github.com/gorilla/websocket"
	"gogame/gameconfig"
	"gogame/lib"
	"gogame/logger"
	"gogame/pb"
	"gogame/server/rpcx"
	"gogame/server/rpcx/client"
	"strings"
	"time"
)

type GatewayApp struct {
	Service               *Service
	Listen                string
	Id                    string          // 唯一id
	Uid                   string          // 玩家uid
	WebSocketConn         *websocket.Conn // websocket 链接对象
	GateWayType           string          // gateway类型
	OutMsgChan            chan []byte     // 写chan
	CloseChan             chan struct{}   // 断开chan
	Stop                  uint16          // 服务是否停止
	DefaultWorkerUrl      string          // 默认分配的workerUrl
	DefaultCrossWorkerUrl string          // 默认分配的跨服workerUrl
	OtherLoginGatewayUrl  string          // 他人登陆的gatewayUrl
	InLoginQueue          bool            // 是否在登陆队列中
	ApiLockSet            *ApiLockSet     // 接口锁
	LockTimeOut           int64           // 锁超时时间
	LastRequestApi        string          // 最后一次请求的api
	LoginTime             int64           // 登陆时间
}

func NewGatewayApp(service *Service, gType string) *GatewayApp {
	return &GatewayApp{
		Id:          lib.Sys.GetUUid(),
		Service:     service,
		GateWayType: gType,
		Listen:      service.GetListen(),
		OutMsgChan:  make(chan []byte, 1000),
		CloseChan:   make(chan struct{}),
		ApiLockSet:  NewApiLockSet(),
		LockTimeOut: 30,
	}
}

// IsStop 服务是否停止
func (s *GatewayApp) IsStop() bool {
	if s.Stop == 1 {
		return true
	}
	return false
}

// OnOpen 连接到来
func (s *GatewayApp) OnOpen(conn *websocket.Conn) {
	s.GateWayType = "gateway"
	s.WebSocketConn = conn
	go s.WriteLoop()

	s.ResponseAny("websocket connect success!!")

}

// OnClose 连接断开
func (s *GatewayApp) OnClose() {
	if s.IsStop() {
		return
	}

	s.Service.LoginQueue.RoleCreateOver(s)
	s.Stop = 1
	close(s.CloseChan)

	if s.Uid != "" {
		// 玩家登出逻辑,非被人挤下线
		if s.OtherLoginGatewayUrl == "" {
			_request := pb.NewRequest()
			_request.ModuleName = "user"
			_request.ApiName = "LoginOut"
			s.Notice(s.FmtRequest(_request))
		}

		// 移除玩家conn对象
		s.Service.RpcManage.DelUser(s)

		s.Uid = ""

	}

	s.WebSocketConn.Close()

}

// WriteLoop 回复消息到客户端
func (s *GatewayApp) WriteLoop() {
	var msg []byte
	for {
		select {
		case msg = <-s.OutMsgChan:
			//logger.Print("websocket 发送msg: %s", msg)
			//err := s.WebSocketConn.WriteMessage(websocket.TextMessage, msg)
			err := s.WebSocketConn.WriteMessage(websocket.BinaryMessage, msg)
			if err != nil {
				logger.GatewayLog.Warn(fmt.Sprintf("gatewap app send error!! -> %s", err))
			}
		case <-s.CloseChan:
			goto End
		}
	}
End:
	//fmt.Println("WriteLoop end...")

}

// Send 发送消息
func (s *GatewayApp) Send(msg []byte) {
	s.OutMsgChan <- msg
}

// SetUid 登陆成功,conn绑定uid
func (s *GatewayApp) SetUid(uid string) {
	s.Response("loginSuccess", "")
	s.Uid = uid
	s.LoginTime = lib.Time.Now()
	s.Service.RpcManage.SetUser(s)
	s.Service.LoginQueue.RoleCreateOver(s)
}

// FmtResponse 格式化response
func (s *GatewayApp) FmtResponse(response *rpcx.Response) *pb.Response {
	_response := pb.NewResponse()
	_response.S = response.S
	_response.ErrorMsg = response.ErrorMsg
	_response.ResponseApi = response.ResponseApi
	_response.Data = response.Data
	_response.StringMsg = response.StringMsg
	return _response
}

// ResponseAny 发送any消息到客户端
func (s *GatewayApp) ResponseAny(msg any) {
	s.WebSocketConn.WriteMessage(websocket.TextMessage, lib.Dumps(msg))
	//s.Response(msg, "")
}

// ResponseJson 发送json消息
func (s *GatewayApp) ResponseJson(msg any) {
	s.Response(msg, "")
}

// ResponseError 发送错误
func (s *GatewayApp) ResponseError(code int64, errorMsg string) {
	// 格式化成pb消息
	_pbMsg := rpcx.NewResponse(
		pb.ApiResponse{
			S:        code,
			ErrorMsg: errorMsg,
		},
	)
	s.Response(_pbMsg, "")
}

// Response 发送消息
func (s *GatewayApp) Response(msg any, err string) {
	//logger.Print("gateway[%s] 回复客户端数据--> 【%+v】", s.Listen, msg)

	_pbMsg, _isPb := msg.(*rpcx.Response)
	// 是pb消息
	if _isPb {
		// 如果是login且pb的uid不为空且ws的uid为空 就设置ws uid
		if _pbMsg.ResponseApi == "user.Login" && s.Uid == "" && _pbMsg.Uid != "" {
			s.SetUid(_pbMsg.Uid)
		}
		// 接口解锁
		if _pbMsg.ResponseApi != "" {
			s.ApiLockSet.DelLock(_pbMsg.ResponseApi)
		} else {
			s.ApiLockSet.DelLock(s.LastRequestApi)
		}
	} else {
		// 格式化成pb消息
		_pbMsg = rpcx.NewResponse(
			pb.ApiResponse{
				StringMsg: lib.Dumps(msg),
			},
		)
	}

	if err != "" {
		_pbMsg.ErrorMsg = err
	}

	// 优先处理提前返回的消息
	if len(_pbMsg.FirstResponse) > 0 {
		for _, firstPbMsg := range _pbMsg.FirstResponse {
			s.Send(lib.Marshal(firstPbMsg))
		}
	}

	_response := s.FmtResponse(_pbMsg)
	// rpcx.response回收
	rpcx.PutResponse(_pbMsg)

	_sendMsg := lib.Marshal(_response)
	// pb.response回收
	pb.PutResponse(_response)

	s.Send(_sendMsg)

}

// ServiceUnAvailable 服务不可用
func (s *GatewayApp) ServiceUnAvailable(code int64) {
	s.ResponseError(code, "服务器开小差啦，请稍后再试")
}

// OnMessage 收到消息
func (s *GatewayApp) OnMessage(msg []byte) {
	s.parseMessage(msg)
}

// parseServiceKey 根据模块解析ServiceKey
func parseServiceKey(moduleName string) string {
	if strings.Contains(moduleName, "cross") {
		return rpcx.CrossApi
	}
	return rpcx.Api
}

// CheckSec 接口秘钥验证
func (s *GatewayApp) CheckSec(request *pb.Request) bool {
	_gameVer := gameconfig.ServerConfig.GetGameVer()
	// debug版本不验证，方便测试
	if _gameVer != "debug" {
		// 秘钥格式：“CE:” + “消息Md5之后的字符串(32位)”  消息 = Request.ModuleName + Request.ApiName + 游戏唯一key
		if !strings.HasPrefix(request.Sec, "CE:") || len(request.Sec) < 35 {
			s.ResponseError(-1002, "秘钥格式错误")
			return false
		}

		_clientMd5Key := request.Sec[3:35]
		_serverMd5Key := lib.Md5(request.ModuleName + request.ApiName + gameconfig.ServerConfig.GameKey)
		if strings.ToLower(_serverMd5Key) != strings.ToLower(_clientMd5Key) {
			s.ResponseError(-1003, "秘钥错误")
			return false
		}
	}
	return true
}

// CheckApiLock 接口锁检测
func (s *GatewayApp) CheckApiLock(request *pb.Request) bool {
	_api := fmt.Sprintf("%s.%s", request.ModuleName, request.ApiName)
	_lockTime := s.ApiLockSet.GetLockTime(_api)
	_nt := lib.Time.Now()
	// 锁存在
	if _lockTime > 0 {
		// 锁还未超时
		if _nt-_lockTime < s.LockTimeOut {
			return false
		}
	}

	// 设置接口锁
	s.LastRequestApi = _api
	s.ApiLockSet.SetLockTime(_api, lib.Time.Now())

	return true
}

// ParseMessage 解析消息
func (s *GatewayApp) parseMessage(msg []byte) {
	request := pb.NewRequest()
	err := lib.Unmarshal(msg, request)
	if err != nil {
		s.ResponseError(-1001, "错误的协议内容")
		return
	}

	// 接口锁检测
	if !s.CheckApiLock(request) {
		s.ResponseError(-99, "访问太频繁了，请稍后再试")
		return
	}

	// 接口秘钥验证
	if !s.CheckSec(request) {
		return
	}

	// 根据模块解析对应需要的服务key api还是cross_api
	_serviceName := parseServiceKey(request.ModuleName)
	_client := s.getRpcXClient(_serviceName)
	// client不可用
	if _client == nil {
		s.ServiceUnAvailable(-80)
		return
	}

	_apiRequest := s.FmtRequest(request)
	_isLocal := true
	if strings.Contains(_serviceName, "cross") {
		_isLocal = false
	}
	// 本服请求
	if _isLocal {
		// 默认workerUrl存活校验
		if s.DefaultWorkerUrl != "" {
			if !_client.Selector.HasServer(s.DefaultWorkerUrl) {
				s.ServiceUnAvailable(-81)
				return
			}
		}
	} else {
		// 跨服请求
		if s.DefaultCrossWorkerUrl == "" {
			// 随机一个跨服worker
			s.DefaultCrossWorkerUrl = _client.Selector.RandWorkerUrl()
		}

		// 还没有可用的worker
		if s.DefaultCrossWorkerUrl == "" {
			s.ServiceUnAvailable(-85)
			return
		}
		_apiRequest.WorkerUrl = s.DefaultCrossWorkerUrl
	}

	//logger.Print("gateway[%s] 收到客户端发送的数据--> 【%v】", s.Listen, request)
	s.doApi(_client, _apiRequest)

}

// FmtRequest 格式化接口请求信息
func (s *GatewayApp) FmtRequest(request *pb.Request) *pb.ApiRequest {
	_apiRequest := pb.NewApiRequest()
	_apiRequest.ModuleName = request.ModuleName
	_apiRequest.ApiName = request.ApiName
	_apiRequest.Data = request.Data

	_apiRequest.Api = fmt.Sprintf("%s.%s", request.ModuleName, request.ApiName)
	_apiRequest.Ip = s.WebSocketConn.RemoteAddr().String()
	_apiRequest.GatewayUrl = s.Listen
	_apiRequest.Uid = s.Uid
	_apiRequest.WorkerUrl = s.DefaultWorkerUrl

	// request回收
	pb.PutRequest(request)

	return _apiRequest
}

// WillLogin 登陆队列检测
func (s *GatewayApp) WillLogin() {
	_delayTime := s.Service.LoginQueue.WillLogin(s)
	// 小于2s前端不需要等待
	_sendDelay := _delayTime / 1000
	if _sendDelay < 2 {
		_sendDelay = 0
	}
	// 通知前端等待
	s.ResponseJson(map[string]any{"await": _sendDelay})

	// 排队ing...
	if _delayTime > 0 {
		time.Sleep(time.Millisecond * time.Duration(_delayTime))
	}
}

// doApi 接口处理
func (s *GatewayApp) doApi(client *client.RpcXClient, request *pb.ApiRequest) {
	// 不是登录接口需要检测是否登录
	if request.Api != "user.Login" {
		// 未登陆或者被挤下线了
		if s.DefaultWorkerUrl == "" || s.Uid == "" {
			s.ResponseError(-1004, "请先登录")
			return
		}

	} else {
		// 不能重复登录
		if s.Uid != "" {
			s.ResponseError(-1005, "已登录")
			return
		}

		// 登陆排队
		s.WillLogin()

		// login的时候分配一个worker去访问
		_randWorkerUrl := client.Selector.RandWorkerUrl()
		// 还没有可用的worker
		if _randWorkerUrl == "" {
			s.ServiceUnAvailable(-82)
			return
		}
		s.DefaultWorkerUrl = _randWorkerUrl
		request.WorkerUrl = _randWorkerUrl

	}

	s.SendRequest(client, request, s.Response)
}

// getRpcXClient 获取一个rpcXClient
func (s *GatewayApp) getRpcXClient(key string) *client.RpcXClient {
	_client := s.Service.RpcManage.GetRpcXClient(key)
	if !s.CheckRpcXClient(_client) {
		return nil
	}
	return _client
}

// CheckRpcXClient rpcXClient检测
func (s *GatewayApp) CheckRpcXClient(c *client.RpcXClient) bool {
	if c.State != client.Run {
		return false
	}

	return true
}

// SendRequest 发送接口请求
func (s *GatewayApp) SendRequest(client *client.RpcXClient, request *pb.ApiRequest, callbackFunc client.GatewayCallFunc) {
	_response := rpcx.NewResponse(pb.ApiResponse{})
	err := client.CallBack("DoApi", request, _response, callbackFunc)
	if err != nil {
		logger.GatewayLog.Warn(fmt.Sprintf("gateway.SendRequest fail!!\nerr-->%s", err.Error()))
		s.ServiceUnAvailable(-83)
	}

}

// Notice 异步通知
func (s *GatewayApp) Notice(request *pb.ApiRequest) {
	_rpcXClient := s.getRpcXClient("api")
	if _rpcXClient == nil {
		s.ServiceUnAvailable(-84)
	}
	if _rpcXClient == nil {
		logger.Print("gateway.handle.Notice fail _rpcXClient is nil!!")
		return
	}

	_rpcXClient.Notice("DoApi", request)

}
